﻿#region "Copyright (C) Lenny Granovsky. 2011-2013"
//    This library provides extended cache capabilities to the ASP.NET applications.
//
//                Copyright (C) Lenny Granovsky. 2011-2013. 
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 2 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
//    This program comes with ABSOLUTELY NO WARRANTY.
//    This is free software, and you are welcome to redistribute it
//    under certain conditions;
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO.Compression;
using FlexiCache.Utilities;
using System.Diagnostics;

namespace FlexiCache
{
    /// <summary>
    /// Base class providing data manupulation methods to Cache Storage Adapter class(es).
    /// </summary>
    public class CacheDataUtility : CacheStorageConfigInfo
	{
        public CacheDataUtility()
            : base()
        {
        }

        protected static string ComputeCacheStorageKey(string value)
        {
            MD5CryptoServiceProvider cryptoServiceProvider = new MD5CryptoServiceProvider();
            byte[] bytes = Encoding.UTF8.GetBytes(value);
            StringBuilder builder = new StringBuilder(value.Length.ToString().PadLeft(8, '0') + "-");

            bytes = cryptoServiceProvider.ComputeHash(bytes);

            foreach (byte b in bytes)
                builder.Append(b.ToString("x2").ToLower());

            return builder.ToString();
        }

        protected byte[] Serialize(object entry)
        {
            BinaryFormatter formatter = new BinaryFormatter();
            using (MemoryStream stream = new MemoryStream())
            {
                formatter.Serialize(stream, entry);
                stream.Flush();

                if (this.UseCompression)
                {
                    return this.CompressData(stream.ToArray());
                }
                return stream.ToArray();
            }
        }

        protected object Deserialize(byte[] serializedEntry)
        {
            if (this.UseCompression)
            {
                serializedEntry = this.DecompressData(serializedEntry);
            }
            BinaryFormatter formatter = new BinaryFormatter();
            using (MemoryStream stream = new MemoryStream(serializedEntry))
            {
                stream.Position = 0;
                return formatter.Deserialize(stream);
            }
        }

        protected void HandleException(Exception ex)
        {
            ErrorUtility.HandleExceptionInternally(ex);
        }

        /// <summary>
        /// Method performs compression and returns compressed byte array.
        /// </summary>
        /// <param name="uncompressedData">Data to compress.</param>
        /// <returns>A byte array of compressed data.</returns>
        protected byte[] CompressData(byte[] uncompressedData)
        {
            using (MemoryStream mem = new MemoryStream())
            {
                CompressionMode mode = CompressionMode.Compress;
                using (DeflateStream gzip = new DeflateStream(mem, mode, true))
                {
                    gzip.Write(uncompressedData, 0, uncompressedData.Length);
                    gzip.Flush();
                }
                return mem.ToArray();
            }
        }

        /// <summary>
        /// Method decompresses data and returns byte array.
        /// </summary>
        /// <param name="compressedData">Data to decompress.</param>
        /// <returns>A byte array of decompressed data.</returns>
        protected byte[] DecompressData(byte[] compressedData)
        {
            DeflateStream gzip = null;
            using (MemoryStream inputMem = new MemoryStream())
            {
                inputMem.Write(compressedData, 0, compressedData.Length);
                inputMem.Position = 0;
                CompressionMode mode = CompressionMode.Decompress;
                gzip = new DeflateStream(inputMem, mode, true);
                using (MemoryStream outputMem = new MemoryStream())
                {
                    byte[] buf = new byte[1024];
                    int byteRead = -1;
                    byteRead = gzip.Read(buf, 0, buf.Length);
                    while (byteRead > 0)
                    {
                        outputMem.Write(buf, 0, byteRead);
                        byteRead = gzip.Read(buf, 0, buf.Length);
                    }
                    gzip.Flush();
                    gzip.Close();
                    return outputMem.ToArray();
                }
            }
        }

        /// <summary>
        /// Method converts an array of meta tags into a single string that can be used to store in the cache storage.
        /// </summary>
        /// <param name="metaTags">The meta tags associated with the cached data.</param>
        /// <returns>Single metadata string that can be used to store in the cache storage.</returns>
        protected string BuildMetadata(string[] metaTags)
        {
            if (metaTags == null)
                return "";
            if (metaTags.Length == 0)
                return "";
            string metadata = string.Format("[{0}]", string.Join("][", metaTags));
            if (metadata.Length > CachingConfiguration.MaxMetadataSize)
            {
#if TRACE
                Trace.WriteLine(string.Format("{0}.BuildMetadata. The metadata '{0}' will be limited by {1} chars.", this.TracePrefix, metadata, CachingConfiguration.MaxMetadataSize.ToString()));
#endif
                int pos = metadata.IndexOf("][", metadata.Length - CachingConfiguration.MaxMetadataSize);
                if (pos < 0)
                    metadata = metadata.Remove(0, metadata.Length - CachingConfiguration.MaxMetadataSize);
                else
                    metadata = metadata.Substring(pos + 1);
            }
            return metadata;
        }
    }
}
